package br.com.lopes.gci.dao.impl;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import br.com.lopes.gci.exception.DAOException;
import br.com.lopes.gci.dao.IDAO;

public abstract class DAO<T, K extends Serializable> extends HibernateDaoSupport implements IDAO<T, K> {
    protected Class<?> clazz;

    public DAO() {
        clazz = (Class<?>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }

	public void delete(T object) throws DAOException {
		try {
			getHibernateTemplate().delete(object);
			getHibernateTemplate().flush();
		} catch (DataAccessException e) {
			throw new DAOException("Erro ao tentar remover o(a) " + clazz.getSimpleName() + " " + e.getMessage());
		} catch (Exception e) {
			throw new DAOException("Erro ao tentar remover o(a) " + clazz.getSimpleName() + " " + e.getMessage());
		}
	}

	public int delete(final String namedQuery, final String paramName, final Object param) throws DAOException {
		try {
			Integer result = (Integer) getHibernateTemplate().execute(new HibernateCallback() {		
				public Object doInHibernate(Session session) throws HibernateException, SQLException {				
					Query q = session.getNamedQuery(namedQuery);
					q.setParameter(paramName, param);
					int ret =  q.executeUpdate();
					session.flush();				
					return ret;
				}
			});
			return result;
		} catch (DataAccessException e) {
			throw new DAOException("Erro ao tentar remover o(a) " + clazz.getSimpleName() + " " + e.getMessage());
		} catch (Exception e) {
			throw new DAOException("Erro ao tentar remover o(a) " + clazz.getSimpleName() + " " + e.getMessage());
		}
	}

	public void delete(final String namedQuery, final String[] paramNames, final Object[] params) throws DAOException {
		try {
			if (paramNames == null || params == null) {
				throw new Exception("Parameters cannot be null");
			}

			if (paramNames.length != params.length) {
				throw new Exception("Names and values has to have the same size");
			}

			getHibernateTemplate().execute(new HibernateCallback() {		
				public Object doInHibernate(Session session) throws HibernateException, SQLException {
					Query q = session.getNamedQuery(namedQuery);
					int index = 0;
					for (String paramName : paramNames) {
						q.setParameter(paramName, params[index]);
						++ index;
					}
					q.executeUpdate();
					session.flush();
					return Boolean.TRUE;
				}
			});
		} catch (DataAccessException e) {
			throw new DAOException("Erro ao tentar remover o(a) " + clazz.getSimpleName() + " " + e.getMessage());
		} catch (Exception e) {
			throw new DAOException("Erro ao tentar remover o(a) " + clazz.getSimpleName() + " " + e.getMessage());
		}
	}

	public void mergeMany(final Collection<T> entities) throws DAOException {
		try {
			getHibernateTemplate().execute(new HibernateCallback() {
				public Object doInHibernate(Session session) throws HibernateException {
					for (Iterator<T> it = entities.iterator(); it.hasNext();) {
						session.merge(it.next());
						session.flush();
					}
					return null;
				}
			});
		} catch (DataAccessException e) {
			throw new DAOException("Erro ao tentar inserir os(as) " + clazz.getSimpleName() + " " + e.getMessage());
		} catch (Exception e) {
			throw new DAOException("Erro ao tentar inserir os(as) " + clazz.getSimpleName() + " " + e.getMessage());
		}
	}

	public void saveOrUpdateMany(final Collection<T> entities) throws DAOException {
		try {
			getHibernateTemplate().execute(new HibernateCallback() {
				public Object doInHibernate(Session session) throws HibernateException {
					for (Iterator<T> it = entities.iterator(); it.hasNext();) {
						session.saveOrUpdate(it.next());
						session.flush();
					}
					return null;
				}
			});
		} catch (DataAccessException e) {
			throw new DAOException("Erro ao tentar inserir os(as) " + clazz.getSimpleName() + " " + e.getMessage());
		} catch (Exception e) {
			throw new DAOException("Erro ao tentar inserir os(as) " + clazz.getSimpleName() + " " + e.getMessage());
		}
	}

	public void createMany(final Collection<T> entities) throws DAOException {
		try {
			getHibernateTemplate().execute(new HibernateCallback() {
				public Object doInHibernate(Session session) throws HibernateException {
					for (Iterator<T> it = entities.iterator(); it.hasNext();) {
						session.persist(it.next());
						session.flush();
					}
					return null;
				}
			});
		} catch (DataAccessException e) {
			throw new DAOException("Erro ao tentar inserir os(as) " + clazz.getSimpleName() + " " + e.getMessage());
		} catch (Exception e) {
			throw new DAOException("Erro ao tentar inserir os(as) " + clazz.getSimpleName() + " " + e.getMessage());
		}
	}

	@SuppressWarnings("unchecked")
	public List<T> findAll() throws DAOException {
		DetachedCriteria criteria = DetachedCriteria.forClass(clazz);
		criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

		try {
			List<T> lista = getHibernateTemplate().findByCriteria(criteria);
			return lista;
		} catch (DataAccessException e) {
			throw new DAOException("Erro ao tentar listar os(as) " + clazz.getSimpleName() + " " + e.getMessage());
		} catch (Exception e) {
			throw new DAOException("Erro ao tentar listar os(as) " + clazz.getSimpleName() + " " + e.getMessage());
		}
	}

	@SuppressWarnings("unchecked")
	public List<T> findByCriteria(DetachedCriteria criteria) throws DAOException {
		try {
			return getHibernateTemplate().findByCriteria(criteria);
		} catch (DataAccessException e) {
			throw new DAOException("Erro ao tentar listar os(as) " + clazz.getSimpleName() + " " + e.getMessage());
		} catch (Exception e) {
			throw new DAOException("Erro ao tentar listar os(as) " + clazz.getSimpleName() + " " + e.getMessage());
		}
	}

	@SuppressWarnings("unchecked")
	public T findByPK(K pk) throws DAOException {
		try {
			return (T) getHibernateTemplate().load(clazz, pk);
		} catch (DataAccessException e) {
			throw new DAOException("Erro ao tentar carregar o(a) " + clazz.getSimpleName() + " " + e.getMessage());
		} catch (Exception e) {
			throw new DAOException("Erro ao tentar carregar o(a) " + clazz.getSimpleName() + " " + e.getMessage());
		}
	}

	public T insert(T object) throws DAOException {
		try {
			getHibernateTemplate().saveOrUpdate(object);
			getHibernateTemplate().flush();
			refreshObject(object);
			return object;
		} catch (DataAccessException e) {
			throw new DAOException("Erro ao tentar salvar o(a) " + clazz.getSimpleName() + " " + e.getMessage());
		} catch (Exception e) {
			throw new DAOException("Erro ao tentar salvar o(a) " + clazz.getSimpleName() + " " + e.getMessage());
		}
	}

	@SuppressWarnings("unchecked")
	public T merge(T object) throws DAOException {
		try {
			Object obj = getHibernateTemplate().merge(object);
			getHibernateTemplate().flush();
			return (T) obj;
		} catch (DataAccessException e) {
			throw new DAOException("Erro ao tentar atualizar o(a) " + clazz.getSimpleName() + " " + e.getMessage());
		} catch (Exception e) {
			throw new DAOException("Erro ao tentar atualizar o(a) " + clazz.getSimpleName() + " " + e.getMessage());
		}
	}

	public void update(T object) throws DAOException {
		try {
			getHibernateTemplate().update(object);
			getHibernateTemplate().flush();
		} catch (DataAccessException e) {
			throw new DAOException("Erro ao tentar atualizar o(a) " + clazz.getSimpleName() + " " + e.getMessage());
		} catch (Exception e) {
			throw new DAOException("Erro ao tentar atualizar o(a) " + clazz.getSimpleName() + " " + e.getMessage());
		}
	}

	@SuppressWarnings("unchecked")
	public List<T> getScrollableList(DetachedCriteria criteria, int firstResult, int maxResults) throws DAOException {
		try {
			return (List<T>) getHibernateTemplate().findByCriteria(criteria, firstResult, maxResults);			
		} catch (DataAccessException e) {
			throw new DAOException("Erro ao tentar listar os(as) " + clazz.getSimpleName() + " "  + e.getMessage());
		} catch (Exception e) {
			throw new DAOException("Erro ao tentar listar os(as) " + clazz.getSimpleName() + " "  + e.getMessage());
		}
	}
	@SuppressWarnings("unchecked")
	public List<T> max(final String campo) throws DAOException{
		try {
	        final Object obj = getHibernateTemplate().execute(new HibernateCallback() {
				public Object doInHibernate(Session session) throws HibernateException, SQLException {
	            	Criteria criteria = session.createCriteria(clazz);
	            	criteria.setProjection(Projections.max(campo));
	            	return criteria.uniqueResult(); 
	            }
	        });
	        List<T> list = (List<T>)getHibernateTemplate().executeFind(new HibernateCallback() {
				public List<T> doInHibernate(Session session) throws HibernateException, SQLException {
	            	Criteria criteria = session.createCriteria(clazz);
	            	criteria.add(Restrictions.eq(campo, obj));
	            	return criteria.list(); 
	            }
	        });
	        
	        getHibernateTemplate().flush();
	        return list;
		} catch (DataAccessException e) {
			throw new DAOException("Erro ao contar " + clazz.getSimpleName() + " "  + e.getMessage());
		} catch (Exception e) {
			throw new DAOException("Erro ao contar " + clazz.getSimpleName() + " "  + e.getMessage());
		}
	}
	
	public Integer count() throws DAOException {
		try {
	        return (Integer) getHibernateTemplate().execute(new HibernateCallback() {
				public Object doInHibernate(Session session) throws HibernateException, SQLException {
	            	Criteria criteria = session.createCriteria(clazz);
	            	criteria.setProjection(Projections.rowCount());
	            	return criteria.uniqueResult(); 
	            }
	        });			
		} catch (DataAccessException e) {
			throw new DAOException("Erro ao contar " + clazz.getSimpleName() + " "  + e.getMessage());
		} catch (Exception e) {
			throw new DAOException("Erro ao contar " + clazz.getSimpleName() + " "  + e.getMessage());
		}
	}

	public Integer countByCriteria(final DetachedCriteria criteria) throws DAOException {
		try {
	        return (Integer) getHibernateTemplate().execute(new HibernateCallback() {
				public Object doInHibernate(Session session) throws HibernateException, SQLException {
					Criteria dCriteria = criteria.getExecutableCriteria(session);
					dCriteria.setProjection(Projections.rowCount());
	            	return dCriteria.uniqueResult();
	            }
	        });			
		} catch (DataAccessException e) {
			throw new DAOException("Erro ao contar " + clazz.getSimpleName() + " "  + e.getMessage());
		} catch (Exception e) {
			throw new DAOException("Erro ao contar " + clazz.getSimpleName() + " "  + e.getMessage());
		}
	}
	public void cleanSession() throws DAOException {
		try {
			getHibernateTemplate().clear();
		} catch (DataAccessException e) {
			throw new DAOException("Erro ao tentar limpar a sessao " + clazz.getSimpleName() + " " + e.getMessage());
		} catch (Exception e) {
			throw new DAOException("Erro ao tentar limpar a sessao " + clazz.getSimpleName() + " " + e.getMessage());
		}
	}
	
	public void refreshObject(T object) throws DAOException {
		try {
			getHibernateTemplate().refresh(object);
		} catch (DataAccessException e) {
			throw new DAOException("Erro ao tentar atualizar objeto da sessao " + clazz.getSimpleName() + " " + e.getMessage());
		} catch (Exception e) {
			throw new DAOException("Erro ao tentar salvar o(a) " + clazz.getSimpleName() + " " + e.getMessage());
		}
	}
	
}